package cn.wizzer.app.user.modules.service.impl;

import cn.wizzer.app.sys.modules.models.SysParam;
import cn.wizzer.app.sys.modules.services.SysParamService;
import cn.wizzer.app.sys.modules.services.SysSensitiveService;
import cn.wizzer.app.user.modules.models.LiveBroadcast;
import cn.wizzer.app.user.modules.models.User;
import cn.wizzer.app.user.modules.models.UserAccount;
import cn.wizzer.app.user.modules.models.UserRestriction;
import cn.wizzer.app.user.modules.models.enums.BizTypeEnum;
import cn.wizzer.app.user.modules.models.enums.EquipmentEnum;
import cn.wizzer.app.user.modules.models.enums.UserColEnum;
import cn.wizzer.app.user.modules.models.vo.AnchorVo;
import cn.wizzer.app.user.modules.services.LiveBroadcastService;
import cn.wizzer.app.user.modules.services.UserLogService;
import cn.wizzer.app.user.modules.services.UserRestrictionService;
import cn.wizzer.app.user.modules.services.UserService;
import cn.wizzer.app.user.modules.services.account.UserAccountRecordService;
import cn.wizzer.app.user.modules.services.account.UserAccountService;
import cn.wizzer.app.user.util.TokenUtilImpl;
import cn.wizzer.framework.base.Result;
import cn.wizzer.framework.base.service.BaseServiceImpl;
import cn.wizzer.framework.base.service.CommonDBService;
import cn.wizzer.framework.page.Pagination;
import cn.wizzer.framework.util.InviteCodeUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.fastjson.JSONObject;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.util.ByteSource;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.Times;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import redis.clients.jedis.ScanParams;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@IocBean(args = {"refer:dao"})
@Service(interfaceClass = UserService.class)
public class UserServiceImpl extends BaseServiceImpl<User> implements UserService {

    public UserServiceImpl(Dao dao) {
        super(dao);
    }

    @Inject
    private TokenUtilImpl tokenUtil;

    @Inject
    private UserAccountService accountService;

    @Inject
    private RedisService redisService;

    @Reference
    @Inject
    private SysSensitiveService sysSensitiveService;

    @Inject
    private CommonDBService dbService;

    @Inject
    private UserRestrictionService restrictionService;

    @Inject
    private UserLogService userLogService;

    @Inject
    @Reference
    private SysParamService sysParamService;

    @Inject
    private LiveBroadcastService liveBroadcastService;

    @Inject
    private UserAccountRecordService accountRecordService;

    @Override
    @Aop(TransAop.READ_COMMITTED)
    public void insertUser(User user, String inviteCode) {
        if (null != user.getPassword()) {
            String salt = R.UU32();
            user.setSalt(salt);
            user.setPassword(new Sha256Hash(user.getPassword(), ByteSource.Util.bytes(salt), 1024).toHex());
        }
        // 邀请码不为空，查出关联的用户
        if (Strings.isNotBlank(inviteCode)) {
            User inviteUser = this.fetch(Cnd.where("invite_code", "=", inviteCode));
            if (inviteUser == null) {
                Result.breakWith("邀请码有误");
            }
            // 关联邀请人
            user.setInviter(inviteUser.getId());
        }
        // 生成该用户自己的邀请码
        String myInviteCode = InviteCodeUtil.getInviteCode(Long.parseLong(user.getPhone()));
        user.setInviteCode(myInviteCode);
        // 生成username。取手机号后4位，邀请码后两位，保证唯一
        String username = null;
        try {
            username = "HV" + user.getPhone().substring(7) + myInviteCode.substring(5);
        } catch (StringIndexOutOfBoundsException e) {
            Result.breakWith("手机号码必须是十一位");
        }
        user.setUserName(username);
        user.setCtAt(1L);
        if (count(Cnd.where("phone", "=", user.getPhone())) > 0) {
            Result.breakWith("手机号已存在");
        }
        //全局参数
        SysParam sysParam = sysParamService.fetch();
        //设置用户默认头像 nginx转发规则
        user.setHeadPicture("/file" + sysParam.getDefaultHeadPicture());
        try {
            this.insert(user);
        } catch (Exception e) {
            Result.breakWith("手机号是否已存在");
        }
        UserAccount userAccount = new UserAccount();
        userAccount.setUserId(user.getId());
        accountService.insert(userAccount);

        //初始化限制数据，以全局参数为准
        UserRestriction userRestriction = JSONObject.parseObject(JSONObject.toJSONString(sysParam), UserRestriction.class);
        userRestriction.setUserId(user.getId());
        userRestriction.setOpAt(Times.getTS());
        //新增用户限制记录
        restrictionService.insert(userRestriction);
        //保存缓存
        restrictionService.saveCache(userRestriction);
    }

    @Override
    public void deleteById(String id) {
        this.delete(id);
    }

    @Override
    public Pagination listPage(Class clazz, Integer pageNumber, int pageSize, String sql, String countSql) {
        return dbService.listPage(clazz, pageNumber, pageSize, sql, countSql);
    }

    @Override
    public Pagination userList(Integer userType, Integer agent, String searchKeyword, String[] searchDate, Integer isOnline, Integer attendant, int pageNumber, int pageSize) {
        Sql sql = Sqls.create("select * from user " +
                "$condition ");
        Sql countSql = Sqls.create("select count(1) from user " +
                "$condition ");
        Cnd cnd = Cnd.NEW();
        if (null != userType) {
            cnd.and("anchor", "=", userType);
        }
        if (null != agent) {
            cnd.and("agent", "=", agent);
        }
        if (Strings.isNotBlank(searchKeyword)) {
            searchKeyword = "%" + searchKeyword + "%";
            cnd.and(Cnd.exps("phone", "like", searchKeyword)
                    .or("nickName", "like", searchKeyword));
        }
        if (null != searchDate && searchDate.length != 0) {
            cnd.and("opAt", ">=", Times.d2TS(Times.D(searchDate[0])));
            cnd.and("opAt", "<=", Times.d2TS(Times.D(searchDate[1])));
        }
        if (null != isOnline && !isOnline.equals("")) {
            cnd.and("is_online", "=", isOnline);
        }
        if (null != attendant) {
            cnd.and("attendant", "=", attendant);
        }
        sql.setCondition(cnd);
        sql.appendSourceSql("order by opAt desc");
        countSql.setCondition(cnd);
        return this.listPage(User.class, pageNumber, pageSize, sql.toString(), countSql.toString());
    }

    @Override
    public Pagination userListNew(String conditionData, String searchKeyword, String[] searchDate, int pageNumber, int pageSize) {
        Sql sql = Sqls.create("SELECT * FROM user " +
                "$condition ");
        Sql countSql = Sqls.create("SELECT COUNT(1) FROM user " +
                "$condition ");
        Cnd cnd = Cnd.where("delFlag", "=", 0);
        if (Strings.isNotBlank(conditionData)) {
            String[] split = conditionData.split(",");
            for (String col : split) {
                String colName = UserColEnum.getValByName(col);
                if (Strings.isNotBlank(colName)) {
                    cnd.and(colName, "=", 1);
                }
            }
        }
        if (Strings.isNotBlank(searchKeyword)) {
            searchKeyword = "%" + searchKeyword + "%";
            cnd.and(Cnd.exps("nickName", "LIKE", searchKeyword)
                    .or("phone", "LIKE", searchKeyword)
                    .or("userName", "LIKE", searchKeyword));
        }
        if (null != searchDate && searchDate.length != 0) {
            cnd.and("opAt", ">=", Times.d2TS(Times.D(searchDate[0])));
            cnd.and("opAt", "<=", Times.d2TS(Times.D(searchDate[1])));
        }
        sql.setCondition(cnd);
        sql.appendSourceSql("ORDER BY opAt DESC");
        countSql.setCondition(cnd);
        return this.listPage(pageNumber, pageSize, sql, countSql);
    }

    @Override
    public boolean checkNickName(String nickName) {
        return Strings.isNotBlank(nickName)
                && (nickName.length() >= 4 && nickName.length() <= 10)
                && Pattern.matches("^(?!_)(?!.*?_$)[a-zA-Z0-9_\\u4e00-\\u9fa5]+$", nickName)
                && !sysSensitiveService.filter(nickName).contains("*");

    }

    @Override
    public Pagination inviteList(String appid, int pageNumber, int pageSize) {
        Sql sql = Sqls.create("SELECT id,nickName,headPicture,sign,opAt " +
                "FROM user " +
                "$condition ");
        Sql countSql = Sqls.create("SELECT COUNT(1) FROM user " +
                "$condition ");
        Cnd cnd = Cnd.where("inviter", "=", appid);
        sql.setCondition(cnd);
        sql.appendSourceSql("order by ctAt desc");
        countSql.setCondition(cnd);
        return this.listPage(pageNumber, pageSize, sql, countSql);
    }

    @Override
    @Aop(TransAop.READ_COMMITTED)
    public void updateUser(User user) {
        User oldUser = this.fetch(user.getId());
        //当用户从普通用户设置为主播时，为用户限制中礼物分成设置默认值
        if (!oldUser.isAnchor() && user.isAnchor()) {
            //全局参数
            SysParam sysParam = sysParamService.fetch();
            UserRestriction userRestriction = restrictionService.initCache(user.getId());
            if (userRestriction != null) {
                userRestriction.setGiftRate(sysParam.getGiftRate());
            }
            //更新用户限制数据
            restrictionService.updateIgnoreNull(userRestriction);
            //更新缓存
            restrictionService.saveCache(userRestriction);
        }
        //修改用户
        this.updateIgnoreNull(user);
    }

    @Override
    public NutMap login(String phone, String password, EquipmentEnum equipment, String ip) {
        // 根据用户ID，生成token，返回api（userId)和token
//            //错误次数限制
        String key = "user:login:" + phone;
        String times = redisService.get(key);
        if ("0".equals(times))
            Result.breakWith("错误次数过多请明日重试或重置密码");

        User user = this.fetch(Cnd.where("phone", "=", Strings.trim(phone)));

        if (user == null) {
            Result.breakWith("手机号码未注册");
        }
        String encryption = new Sha256Hash(password, ByteSource.Util.bytes(user.getSalt()), 1024).toHex();

        if (!sysParamService.fetch().isDebug()) {
            if (!encryption.equals(user.getPassword())) {
                if (Strings.isNotBlank(times)) {
                    int i = Integer.parseInt(times);
                    times = i - 1 + "";
                } else {
                    times = "4";
                }
                redisService.set(key, times);
                redisService.expire(key, (int) (Times.ams(Times.afterDay()) / 1000 - Times.getTS()));
                if ("0".equals(times)) {
                    Result.breakWith("错误次数过多请明日重试或重置密码");
                } else {
                    Result.breakWith("密码错误,今日还可重试" + times + "次");
                }
            } else {
                //登录错误次数清零
                redisService.del(key);
            }
        }


        if (user.isDisabled()) {
            Result.breakWith("您的账号已被禁用");
        }
        //若用户为离线状态，则更新登录时间
        if (!user.isOnline()) {
            //更新登录时间
            this.update(Chain.make("lastLoginAt", Times.getTS())
                            .add("loginTimes", user.getLoginTimes() + 1)
                            .add("is_online", 1)
                            .add("ip", ip),
                    Cnd.where("id", "=", user.getId()));
        }

        // 通过
        int loginValidTime = sysParamService.fetch().getLoginValidTime();
        NutMap resmap = new NutMap();
        Date date = new Date();
        Calendar c = Calendar.getInstance();
        c.setTime(date);
        c.add(Calendar.MINUTE, +loginValidTime);//设置token有效期为10分钟
        date = c.getTime();
        resmap.addv("expires", loginValidTime * 60);//同时把有效期秒数传递给客户端
        resmap.addv("token", tokenUtil.generateToken(date, user.getId(), equipment));
        resmap.addv("appid", user.getId());
        resmap.addv("nickName", user.getNickName());
        resmap.addv("userName", user.getUserName());
        resmap.addv("HeadPicture", user.getHeadPicture());
        resmap.addv("sign", user.getSign());
        resmap.addv("anchor", user.isAnchor());
        resmap.addv("inviteCode", user.getInviteCode());
        resmap.addv("agent", user.isAgent());
        resmap.addv("attendant", user.isAttendant());
        return resmap;
    }

    @Override
    public void offLine() {
        //当前在线用户
        ScanParams scanParams = new ScanParams();
        scanParams.match("user:token:*");
        scanParams.count(Integer.MAX_VALUE);
        List<String> keys = redisService.scan("0", scanParams).getResult();
        //计算登录时长去重，不重复叠加同一个用户不同设备登录的登录时长
        Map<String, String> map = new HashMap<>();
        for (String key : keys) {
            //当token过期时间到了进行下线处理
            if (System.currentTimeMillis() - Long.parseLong(redisService.get(key)) >= 0) {
                String replace = key.replace("user:token:", "");
                String userId = replace.substring(0, replace.indexOf(":"));
                //下线处理
                redisService.del(key);
                Chain chain = Chain.make("is_online", 0);
                if (Strings.isBlank(map.get(userId))) {
                    //更新登录时长
                    chain.add("loginDuration", calculateLoginDuration(userId));
                    map.put(userId, userId);
                }
                this.update(chain, Cnd.where("id", "=", userId));
            }
        }
    }

    /**
     * 计算用户登录时长，按小时
     *
     * @param userId
     * @return
     */
    @Override
    public BigDecimal calculateLoginDuration(String userId) {
        //计算用户登录时长，按小时
        User user = this.fetch(userId);
        //此次登录时长 = 当前时间 - 最近一次登录时间
        long nowLoginDuration = Times.getTS() - user.getLastLoginAt();
        //按小时计算，此次登录时长 / 秒 / 分 + 历史登录时长
        return BigDecimal.valueOf(nowLoginDuration).divide(BigDecimal.valueOf(60).multiply(BigDecimal.valueOf(60)), 2, RoundingMode.DOWN).add(user.getLoginDuration());
    }

    /**
     * 根据用户id集合获取用户信息
     *
     * @param ids
     * @return
     */
    @Override
    public Map<String, User> findByIds(List<String> ids) {
        List<User> users = this.query(Cnd.where("id", "IN", ids));
        return users.stream().collect(Collectors.toMap(User::getId, user -> user));
    }

    @Override
    public Map<String, String> usersToMapJSONString(List<User> users) {
        return users.stream().collect(Collectors.toMap(User::getId, JSONObject::toJSONString));
    }

    @Override
    public Pagination anchorList(String userId, int pageNumber, int pageSize) {
        Sql sql = Sqls.create("SELECT id, headPicture, nickName, sign, " +
                //是否关注
                "   (SELECT COUNT(1) > 0 FROM user_focus WHERE anchorId = user.id AND userId = @userId AND delFlag = 0) as isFocus " +
                "FROM user " +
                //主播身份
                "WHERE anchor = 1 " +
                "AND delFlag = 0 " +
                //热度倒序
                "ORDER BY anchor_heat DESC");
        sql.setParam("userId", userId);
        Sql countSql = Sqls.create("SELECT COUNT(1) FROM user WHERE anchor = 1 AND delFlag = 0");
        Pagination pagination = dbService.listPage(AnchorVo.class, pageNumber, pageSize, sql.toString(), countSql.toString());
        List<AnchorVo> list = pagination.getList(AnchorVo.class);
        if (list.size() > 0) {
            List<LiveBroadcast> liveBroadcasts = liveBroadcastService.query(Cnd.where("state", "=", 1)
                    .and("userId", "IN", list.stream().map(AnchorVo::getId).collect(Collectors.toList())).and("delFlag", "=", 0));
            if (liveBroadcasts.size() > 0) {
                Map<String, String> liveMap = liveBroadcasts.stream()
                        .collect(Collectors.toMap(LiveBroadcast::getUserId,
                                liveBroadcast -> liveBroadcast.getVideoId() == null ? liveBroadcast.getCategoryId() : liveBroadcast.getVideoId()));
                for (AnchorVo anchorVo : list) {
                    anchorVo.setLiveCode(liveMap.get(anchorVo.getId()));
                }
            }
        }
        return pagination;
    }

    @Override
    @Aop(TransAop.READ_COMMITTED)
    public void userRebateJob(User user) {
        String userId = user.getId();
        String inviter = user.getInviter();
        //当用户存在上级且上级存在时
        if (Strings.isNotBlank(inviter) && this.fetch(inviter) != null) {
            //查询该用户的消费总额，大于100时为上级返利
            Sql sql = Sqls.create("SELECT ABS(IFNULL(SUM(operate_amount), 0)) " +
                    "FROM user_account_record " +
                    "WHERE biz_type IN ('GIFT_ORDER', 'BALL_BET') " +
                    "AND delFlag = 0 " +
                    "AND user_id = @userId ");
            sql.setParam("userId", userId);
            sql.setCallback(Sqls.callback.str());
            this.execute(sql);
            BigDecimal totalCost = new BigDecimal(sql.getString());
            if (totalCost.compareTo(BigDecimal.valueOf(100)) >= 0) {
                //活动--自身消费返利
                accountRecordService.add(userId, BigDecimal.valueOf(10), BizTypeEnum.ACTIVITY_COST_REBATE, userId);
                //活动--邀请消费返利
                accountRecordService.add(inviter, BigDecimal.valueOf(10), BizTypeEnum.ACTIVITY_INVITE_REBATE, userId);
                this.update(Chain.make("is_rebate", 1), Cnd.where("id", "=", userId));
            }
        } else {
            //无上级直接不给予返利
            this.update(Chain.make("is_rebate", 1), Cnd.where("id", "=", userId));
        }
    }

}
